<?php
class wapi_cron extends wapi_mod_base {
	public $run_wait = 15;
	private $polled = false;
	function _construct(){
		if ($this->firstrun){
			$this->db_create();
		}
	}
	private function db_create(){
		global $wapi;
		$sql = "CREATE TABLE cron (
		job_id INTEGER PRIMARY KEY AUTOINCREMENT NOT NULL,
		job_name TEXT UNIQUE NOT NULL,
		user_id TEXT NOT NULL,
		owner_class TEXT NOT NULL,
		owner_function TEXT NOT NULL,
		cmd TEXT NOT NULL,
		cron_tab TEXT NOT NULL,
		time_added TEXT NOT NULL,
		time_last_run TEXT,
		run_cnt INTEGER NOT NULL,
		enabled INTEGER NOT NULL,
		state INTEGER NOT NULL,
		pid_last_run INTEGER,
		result_last_run TEXT,
		state_last_run INTEGER,
		time_run_last_run INTEGER,
		tag TEXT
		)";
		$wapi->db->exec($sql);
		$sql = "CREATE TABLE cron_run (
		id INTEGER PRIMARY KEY AUTOINCREMENT NOT NULL,
		start TEXT UNIQUE NOT NULL,
		last_run TEXT,
		up_time INTEGER,
		end TEXT
		)";
		$wapi->db->exec($sql);
	}
	function job_enabled($job_name, $enabled){
		if ($enabled){
			$this->set_job($job_name,'enabled',1);
		} else {
			$this->set_job($job_name,'enabled',0);
		}
	}
	function rem($job_name) {
		global $wapi;
		$sql = "DELETE FROM cron WHERE job_name = '$job_name'";
		$ret = $wapi->db->exec($sql);
	}
	function rem_all() {
		global $wapi;
		$sql = "DELETE FROM cron";
		$ret = $wapi->db->exec($sql);
	}
	function add($job_name, $cmd, $cron_tab, $tag = '') {
		global $wapi;
		$backtrace = debug_backtrace();
		$cj = array();
		$cj['job_name'] = $job_name;
		$cj['user_id'] = $wapi->user->user_id;
		$cj['owner_class'] = $backtrace[1]['class'];
		$cj['owner_function'] = $backtrace[1]['function'];
		$cj['cmd'] = $cmd;
		$cj['cron_tab'] = $cron_tab;
		$cj['time_added'] = date("Y-m-d H:i:s",time());
		$cj['enabled'] = 1;
		$cj['tag'] = $tag;
		// Save to cron table
		$sql = "INSERT INTO cron (job_name, run_cnt, user_id, owner_class, owner_function, cmd, cron_tab, time_added, enabled, tag, state)
		VALUES ('" .
		$cj['job_name']
		. "',0,'" .
		$cj['user_id']
		. "','" .
		$cj['owner_class']
		. "','" .
		$cj['owner_function']
		. "','" .
		$wapi->db->esc($cj['cmd'])
		. "','" .
		$wapi->db->esc($cj['cron_tab'])
		. "','" .
		$wapi->db->esc($cj['time_added'])
		. "'," .
		$cj['enabled']
		. ",'" .
		$wapi->db->esc($cj['tag'])
		. "',0)";
		$ret = $wapi->db->exec($sql);
		// - Save to cron table
	}
	function get_now(){
		return date("Y-m-d H:i:s",time());
	}
	function get_up_time($format = true){
		$ret = 0;
		$now = $this->now;
		$ls = $this->last_start;
		if ($ls != null){
			$ret = $this->time_diff($ls,$now);

		}
		if ($format){
			return $this->seconds_format($ret);
		}
		return $ret;
	}
	function seconds_format($sec){
		$days = floor($sec / 86400);
		$hours = floor(($sec / 3600) - ($days * 24));
		$minutes = floor(($sec / 60) - ($days * 1440) - ($hours * 60));
		$seconds = $sec % 60;
		if (strlen($days) == 1){
			$days = '0' . $days;
		}
		if (strlen($hours) == 1){
			$hours = '0' . $hours;
		}
		if (strlen($minutes) == 1){
			$minutes = '0' . $minutes;
		}
		if (strlen($seconds) == 1){
			$seconds = '0' . $seconds;
		}
		return "$days:$hours:$minutes:$seconds";
	}
	function run_cron(){
		global $wapi;
		$now = time();
		$s = date("s",$now);
		$i = 0;
		$s_buffer = 1;
		$s = $s + $s_buffer;
		$sched_run = '';
		while ($i + $this->run_wait <= 59) {
			if ($i <= $s && $i + $this->run_wait > $s) {
				$ns = $i + $this->run_wait;
				if (strlen($ns) == 1) {
					$ns = '0' . $ns;
				}
				$sched_run = date("Y-m-d H:i",$now) . ':' . $ns;
				break;
			}
			$i = $i + $this->run_wait;
		}
		if ($sched_run == ''){
			$tt = strtotime(date("Y-m-d H:i",$now));
			$tt = $tt + 60;
			$sched_run = date("Y-m-d H:i:s",$tt);
		}
		$cron_id = $this->rand_string(16);
		$wapi->xset('cron_id',$cron_id,'cron');
		$wapi->xset('sched_run',$sched_run,'cron');
		//time_sleep_until(strtotime($sched_run));
		$tdiff = $this->time_diff(date("Y-m-d H:i:s",time()),$sched_run);
		if ($wapi->proc->cmd_exists('nohup')) {
			$nohup = 'nohup ';
		} else {
			$nohup = '';
		}
		$ce = $wapi->proc->cmd_exists('curl');
		if ($ce) {
			$cmd = 'sleep '.$tdiff.'; '.$nohup.'curl http://localhost/lib/wapi/wapi.php?cron_id=' . $cron_id;
		} else {
			$cmd = 'sleep '.$tdiff.'; '.$nohup.'wget -q -O - http://localhost/lib/wapi/wapi.php?cron_id=' . $cron_id;
		}
		// change the debug string to cause the very next curl call to debug
		$dbgstr = null;
		$catchcurl = $wapi->xget('catch_next_curl','debug');
		if ($catchcurl != $dbgstr){
			$catchcurl = $wapi->xset('catch_next_curl',$dbgstr,'debug');
			$cmd .= '\&XDEBUG_SESSION_START=1';
		}
		//$wapi->proc->run($cmd,-1,'');
		$wapi->proc->run($cmd,-1,'',true);
	}
	function get_job($job_name,$field){
		global $wapi;
		$sql = "SELECT $field FROM cron WHERE job_name = '$job_name'";
		if ($wapi->db->query($sql)) {
			$res = $wapi->db->results;
			$r = $res[0];
			return $r[$field];
		}
		return null;
	}
	function set_job($job_name,$field,$value){
		global $wapi;
		if (is_string($value)){
			$value = $wapi->db->esc($value);
			$sql = "UPDATE cron SET $field = '$value' WHERE job_name = '$job_name'";
		} else {
			$sql = "UPDATE cron SET $field = $value WHERE job_name = '$job_name'";
		}
		$ret = $wapi->db->exec($sql);
	}
	function cron_pause(){
		global $wapi;
		$cron_status = -1;
		$wapi->xset('cron_status',$cron_status,'cron');
	}
	function cron_play(){
		global $wapi;
		$cron_status = 0;
		$wapi->xset('cron_status',$cron_status,'cron');
	}
	function poll_check(){
		global $wapi;
		$now = time();
		$cron_needs_start = false;
		// cron_status = -1 Hold, 0 Stopped, 1 Starting, 2 Running
		// cron_id = random string unique to each cron running instance (only 1 at a time ever)
		$cron_id = $wapi->xget('cron_id','cron');
		$cron_status = $wapi->xget('cron_status','cron');
		$last_start = $wapi->xget('last_start','cron');
		$sched_run = $wapi->xget('sched_run','cron');
		$last_run = $wapi->xget('last_run','cron');
		if ($cron_status == -1){
			return false;
		}
		if ($cron_id == null) {
			$cron_needs_start = true;
		} else {
			if (isset($_REQUEST['cron_id']) && $_REQUEST['cron_id'] == $cron_id){
				$wapi->users->load_user('System');
				if ($cron_status == 1) {
					// cron started successfully
					$cron_status = 2; // Running
					$wapi->xset('cron_status',$cron_status,'cron');
					if ($last_run != null){
						// log the session end if one had been started previously
						$sql = "SELECT * FROM cron_run ORDER BY start DESC LIMIT 0,1";
						if ($wapi->db->query($sql)){
							$res = $wapi->db->results;
							$r = $res[0];
							$up_time = $this->time_diff($r['start'],$last_run);
							$sql = "UPDATE cron_run SET end = '$last_run', up_time = $up_time WHERE id = {$r['id']}";
							$wapi->db->exec($sql);
						}
					} else {
						// must be a wapi new install
					}
					$sql = "INSERT INTO cron_run (start) VALUES ('$last_start')";
					$wapi->db->exec($sql);
				}
				//$this->poll();
				return true;
			}
		}
		if (!$cron_needs_start){
			$ns = date("Y-m-d H:i:s",$now);
			$past_due = $this->time_diff($sched_run,$ns) - $this->run_wait;
			if ($past_due >=  10) {
				$cron_needs_start = true;
			}
		}
		if ($cron_needs_start) {
			// Starting cron
			echo 'Starting cron' . "<br />\n";
			$cron_status = 1; // Starting
			$last_start = date("Y-m-d H:i:s",$now);
			$wapi->xset('cron_status',$cron_status,'cron');
			$wapi->xset('last_start',$last_start,'cron');
			$this->run_cron();
			return false;
		}
		return false;
	}
	function get_last_start(){
		global $wapi;
		$sql = "SELECT * FROM cron_run ORDER BY start DESC LIMIT 0,1";
		if ($wapi->db->query($sql)){
			$res = $wapi->db->results;
			$r = $res[0];
			return $r['start'];
		}
		return null;
	}
	function get_last_run(){
		global $wapi;
		$sql = "SELECT * FROM cron_run ORDER BY start DESC LIMIT 0,1";
		if ($wapi->db->query($sql)){
			$res = $wapi->db->results;
			$r = $res[0];
			return $r['last_run'];
		}
		return null;
	}
//	function get_up_time(){
//		global $wapi;
//		$sql = "SELECT * FROM cron_run ORDER BY start DESC LIMIT 0,1";
//		if ($wapi->db->query($sql)){
//			$res = $wapi->db->results;
//			$r = $res[0];
//			return $r['up_time'];
//		}
//		return null;
//	}
	function poll(){
		if ($this->polled) return false;
		$this->polled = true;
		global $wapi;
		$now = time();
		// Running cron
		// javascript time format to match the php version
		// var now = new Date();
		// var dstr = now.format("yyyy-mm-dd HH:MM:ss");
		$last_run = date("Y-m-d H:i:s",$now);
		$wapi->xset('last_run',$last_run,'cron');
		$sql = "SELECT * FROM cron_run ORDER BY start DESC LIMIT 0,1";
		if ($wapi->db->query($sql)){
			$res = $wapi->db->results;
			$r = $res[0];
			$up_time = $this->time_diff($r['start'],$last_run);
			$sql = "UPDATE cron_run SET last_run = '$last_run', up_time = $up_time WHERE id = {$r['id']}";
			$wapi->db->exec($sql);
		}
		$min = date("i",$now);		// 00 - 59
		$hour = date("H",$now);		// 00 - 23
		$dom = date("d",$now);		// 01 - 31
		$month = date("m",$now); 	// 01 - 12
		$dow = date("D",$now);		// Mon - Sun
		$min_reg = '[0-5][0-9]|[0-9]';
		$hour_reg = '[0-1][0-9]|2[0-3]|[0-9]';
		$dom_reg = '[0-2][0-9]|3[0-1]|[0-9]';
		$month_reg = '0[0-9]|1[0-2]|[0-9]';
		$dow_reg = 'mon|tue|wed|thu|fri|sat|sun';
		$p1 = '(?<min>\*|(?:'.$min_reg.')(?:,(?:'.$min_reg.')){0,59})';
		$p2 = '(?<hour>\*|(?:'.$hour_reg.')(?:,(?:'.$hour_reg.')){0,23})';
		$p3 = '(?<dom>\*|(?:'.$dom_reg.')(?:,(?:'.$dom_reg.')){0,30})';
		$p4 = '(?<month>\*|(?:'.$month_reg.')(?:,(?:'.$month_reg.')){0,11})';
		$p5 = '(?<dow>\*|(?:'.$dow_reg.')(?:,(?:'.$dow_reg.')){0,6})';
		$sp = '(?:\s+)';
		$pat = "^(?:$p1)$sp(?:$p2)$sp(?:$p3)$sp(?:$p4)$sp(?:$p5)";
		$sql = 'SELECT * FROM cron WHERE enabled = 1';
		// - Load all runnning processes from db
		if ($wapi->db->query($sql)){
			$res = $wapi->db->results;
			foreach ($res as $r) {
				if ($r['time_last_run'] != ''){
					if (date("Y-m-d H:i",strtotime($r['time_last_run'])) == date("Y-m-d H:i",$now)) {
						continue;
					}
				}
				$min_match = false;
				$hour_match = false;
				$dom_match = false;
				$month_match = false;
				$dow_match = false;
				if (preg_match_all('#'.$pat.'#i', $r['cron_tab'], $matches)){
					// Minute check
					if ($matches['min'][0] == '*'){
						$min_match = true;
					} elseif (preg_match_all('#'.$min_reg.'#i', $matches['min'][0], $ma)){
						foreach ($ma[0] as $m){
							if (intval($m) == intval($min)) {
								$min_match = true;
								break;
							}
						}
					}
					// Hour check
					if ($matches['hour'][0] == '*'){
						$hour_match = true;
					} elseif (preg_match_all('#'.$hour_reg.'#i', $matches['hour'][0], $ma)){
						foreach ($ma[0] as $m){
							if (intval($m) == intval($hour)) {
								$hour_match = true;
								break;
							}
						}
					}
					// Dom check
					if ($matches['dom'][0] == '*'){
						$dom_match = true;
					} elseif (preg_match_all('#'.$dom_reg.'#i', $matches['dom'][0], $ma)){
						foreach ($ma[0] as $m){
							if (intval($m) == intval($dom)) {
								$dom_match = true;
								break;
							}
						}
					}
					// Month check
					if ($matches['month'][0] == '*'){
						$month_match = true;
					} elseif (preg_match_all('#'.$month_reg.'#i', $matches['month'][0], $ma)){
						foreach ($ma[0] as $m){
							if (intval($m) == intval($month)) {
								$month_match = true;
								break;
							}
						}
					}
					// Dow check
					if ($matches['dow'][0] == '*'){
						$dow_match = true;
					} elseif (preg_match_all('#'.$dow_reg.'#i', $matches['dow'][0], $ma)){
						foreach ($ma[0] as $m){
							if (strtolower($m) == strtolower($dow)) {
								$dow_match = true;
								break;
							}
						}
					}
					// Run if ready
					if ($min_match && $hour_match && $dom_match && $month_match && $dow_match) {
						// Run it!
						$r['run_cnt'] ++;
						$this->set_job($r['job_name'],'run_cnt',$r['run_cnt']);
						$this->set_job($r['job_name'],'time_last_run',$last_run);
						$new_proc = $wapi->proc->run($r['cmd'],-1,$r['tag']);
						$pid = $new_proc->pid;
						$this->set_job($r['job_name'],'pid_last_run',$pid);
						$this->set_job($r['job_name'],'state',1);
					}
				}
			}
		}
		$sql = 'SELECT * FROM cron WHERE state = 1';
		// - Load all runnning processes from db
		if ($wapi->db->query($sql)){
			$res = $wapi->db->results;
			foreach ($res as $r) {
				$pid = $r['pid_last_run'];
				if ($pid > 0){
					$ps = $wapi->proc->get_proc($pid,'state');
					if ($ps != null && $ps < 1){
						$this->set_job($r['job_name'],'state_last_run',$ps);
						$pr = $wapi->proc->get_proc($pid,'result');
						$this->set_job($r['job_name'],'result_last_run',$pr);
						$ptr = $wapi->proc->get_proc($pid,'time_run');
						$this->set_job($r['job_name'],'time_run_last_run',$ptr);
						$this->set_job($r['job_name'],'state',0);
					}
				}
			}
		}
		$this->run_cron();
		return true;
	}
	function rand_string($strsize) {
		srand(time());
		$ret = '';
		for($i=1;$i<=$strsize;$i++) {
			#$char = chr(rand(65, 90));
			$char = chr(rand(97, 122));
			$ret .= $char;
		}
		return $ret;
	}
	function time_diff($firstTime,$lastTime){
		$firstTime = strtotime($firstTime);
		$lastTime = strtotime($lastTime);
		$timeDiff = $lastTime - $firstTime;
		return $timeDiff;
	}
}
?>